/*
 *  Copyright 2008-2011 NVIDIA Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */


#include <cusp/detail/config.h>

namespace cusp
{
  namespace detail
  {
    template<class Operator, class Parameter>
    struct operator_parameter_pair {     
      Parameter parm;
      typedef typename Parameter::ValueType ValueType;
      operator_parameter_pair(const Parameter &p) : parm(p) { }    
      __host__ __device__ 
      ValueType exec(int i) const { return Operator::exec(i, parm); }
    };
  
    template<typename LeftParameter, typename RightParameter>
    struct parameter_pair {    
      LeftParameter left;
      RightParameter right;
      typedef typename LeftParameter::ValueType ValueType;
      parameter_pair(const LeftParameter &l, const RightParameter &r) : left(l), right(r) { }     
    };

    template<class LeftOperator, class RightOperator, class LeftParameter, class RightParameter>
    struct addition_operator{
      typedef typename LeftParameter::ValueType ValueType;
      typedef parameter_pair<LeftParameter, RightParameter> parameters;
      __host__ __device__ static ValueType exec(int i, const parameters &p) {
	return LeftOperator::exec(i,p.left) + RightOperator::exec(i,p.right); 
      }
    };
  
  
    template<typename Array>
    struct array_parameter {
      typename Array::view view;
      typedef typename Array::value_type ValueType;
      typedef typename Array::memory_space memory_space;
      __host__ ValueType value(int i,cusp::host_memory) const { 
	return view[i];
      };

      __device__ ValueType value(int i,cusp::device_memory) const { 
	return thrust::raw_pointer_cast(view.data())[i];
      };
    
      array_parameter(Array &array) :
	view(array){ }    
    };

    template<typename Array>
    struct array_parameter<const Array> {
      typedef typename Array::value_type ValueType;
      typedef typename Array::memory_space memory_space;
      const ValueType * p;
      __host__ __device__ ValueType value(int i) const { 
	return p[i];
      };
    
      array_parameter(const Array &array,cusp::host_memory){
	p = thrust::raw_pointer_cast(&array[0]);
      }    
      array_parameter(const Array &array,cusp::device_memory){
	p = thrust::raw_pointer_cast(&array[0]);
      }    
    };

    template<typename T>
    struct constant_parameter{
      typedef T ValueType;
      ValueType v;
      __host__ __device__ ValueType value(int ) const { 
	return v;
      };    
      constant_parameter(ValueType a):v(a){}
    };

    template<class Parameter>
    struct leaf_operator {
      typedef typename Parameter::ValueType ValueType;
      __host__ __device__ static ValueType exec(int i,  const Parameter &p) {	
	return p.value(i);
      }
    };

    template<typename Array, typename MemorySpace>
    operator_parameter_pair<leaf_operator<array_parameter<Array> >,
			    array_parameter<Array> >
    meta_wrap(Array & a, MemorySpace memory_space){
      return operator_parameter_pair<leaf_operator<array_parameter<Array> >, 
				     array_parameter<Array> >(array_parameter<Array>(a,memory_space));
    }


    operator_parameter_pair<leaf_operator<constant_parameter<float> >,
			    constant_parameter<float> >
    meta_wrap(float a){
      return operator_parameter_pair<leaf_operator<constant_parameter<float> >, 
				     constant_parameter<float> >(constant_parameter<float>(a));
    }
  
    template <typename Operator, typename Parameter, typename T>
    struct fused_operator: public thrust::unary_function<int,T>
    {
      const operator_parameter_pair<Operator,Parameter> func;
      fused_operator(const operator_parameter_pair<Operator,Parameter> _func) : func(_func) {}
      __host__ __device__ T operator()(int i){
	return func.exec(i);
      }
    };


    
    template<class LeftOperator, class LeftParameter, class RightOperator, class RightParameter>
    cusp::detail::operator_parameter_pair<cusp::detail::addition_operator<LeftOperator, 
									  RightOperator,
									  LeftParameter, 
									  RightParameter>, 
					  cusp::detail::parameter_pair<LeftParameter, 
								       RightParameter> >
    operator+(const cusp::detail::operator_parameter_pair<LeftOperator, LeftParameter> &left,
	      const cusp::detail::operator_parameter_pair<RightOperator, RightParameter> &right){
      return cusp::detail::operator_parameter_pair<cusp::detail::addition_operator<LeftOperator, RightOperator,
										   LeftParameter, RightParameter>, 
						   cusp::detail::parameter_pair<LeftParameter, RightParameter> >
      (cusp::detail::parameter_pair<LeftParameter, RightParameter>(left.parm, right.parm));
    };
  }
}

#include <cusp/array1d.h>

namespace cusp
{  
  // this is going to be one ugly type
  template <class Array>
  cusp::detail::operator_parameter_pair<cusp::detail::addition_operator<cusp::detail::leaf_operator<cusp::detail::array_parameter<const Array> >,
									cusp::detail::leaf_operator<cusp::detail::array_parameter<const Array> >, 
									cusp::detail::array_parameter<const Array>,
									cusp::detail::array_parameter< const Array > >, 
					cusp::detail::parameter_pair<cusp::detail::array_parameter<const Array>, 
								     cusp::detail::array_parameter<const Array> > > 
  operator+(const Array& a, const Array& b){
    return cusp::detail::operator+(cusp::detail::meta_wrap(a,typename Array::memory_space()),cusp::detail::meta_wrap(b,typename Array::memory_space()));
  }


  template <typename Array, class RightOperator, class RightParameter>
  cusp::detail::operator_parameter_pair<cusp::detail::addition_operator<cusp::detail::leaf_operator<cusp::detail::array_parameter<const Array> >,
									RightOperator,
									cusp::detail::array_parameter<const Array>,
									RightParameter>,
					cusp::detail::parameter_pair<cusp::detail::array_parameter<const Array>, 
								     RightParameter > > 
  operator+(const Array& a, const cusp::detail::operator_parameter_pair<RightOperator, RightParameter> & b){
    return cusp::detail::meta_wrap(a,typename Array::memory_space()) + b;
  }

  template <class Array, class LeftOperator, class LeftParameter, class ParameterType>
  cusp::detail::operator_parameter_pair<cusp::detail::addition_operator<LeftOperator,
									cusp::detail::leaf_operator<ParameterType >,
									LeftParameter,
									ParameterType >,
					cusp::detail::parameter_pair<LeftParameter,
								     ParameterType > >
  operator+(const cusp::detail::operator_parameter_pair<LeftOperator, LeftParameter> & a, const Array& b){
    if(thrust::detail::is_same<Array,cusp::array1d<float,cusp::device_memory){
      return a + cusp::detail::meta_wrap(b,typename Array::memory_space());
    }
  }  

  template <class LeftOperator, class LeftParameter>
  cusp::detail::operator_parameter_pair<cusp::detail::addition_operator<LeftOperator,
									cusp::detail::leaf_operator<cusp::detail::constant_parameter<typename LeftParameter::ValueType> >,
									LeftParameter,
									cusp::detail::constant_parameter<typename LeftParameter::ValueType> >,
					cusp::detail::parameter_pair<LeftParameter,
								     cusp::detail::constant_parameter<typename LeftParameter::ValueType> > >
  operator+(const cusp::detail::operator_parameter_pair<LeftOperator, LeftParameter> & a, float b){
    return a + cusp::detail::meta_wrap(typename LeftParameter::ValueType(b));
  }  

}
